package edu.siu.math.egut.main;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Scanner;

import edu.siu.math.egut.io.Algorithms;
import edu.siu.math.egut.io.LiE;
import edu.siu.math.egut.io.OutputPref;
import edu.siu.math.egut.util.Group;
import edu.siu.math.egut.util.NonlinearException;
import edu.siu.math.egut.util.NotARootException;
import edu.siu.math.egut.util.Polynomial;
import edu.siu.math.egut.util.Sunipotent;
import edu.siu.math.egut.util.Unipotent;

/**
 * @author josephhundley
 *
 */


public class SpEmbedding {
	// This program computes a matrix r(g) such that 
	// g^{-1} l^{-1}( x|y| z) g l( (x|y).r(g)|z)
	// where l: Y -> Heisenberg is an isomorphism.

    private static final String INSTRUCTIONS = "Input is:\n " +
    		"\t* a root of the Levi\n" +
    		"\t* a string to act as the variable" +
    		"\t* a hand-typed list of roots for U/(U,U).  (The embedding being calculated\n" +
    		"\t\tis sensitive to the order.)\n" +
    		"\t* the string you would use to describe the unipotent radical to LiE\n " +
    		"\t\t(e.g, \"1\" or \"[1,3]\"\n" +
    		"\t* the name of the group.";

    /**
     * 
     * 
     * @param args  String[]  there can be two or three.  
     * if two, should specify a parabolic an an exceptional group.  
     * if three, also specifies a subgroup of the levi. 
     */
    public static void main(String[] args) {
	
	OutputStyle style = OutputStyle.MATHEMATICA;
	// TODO add (optional?) arg that sets this.
	
	String[] input;
	try {
	    input = Algorithms.getCommandsFromFile(args[0]);
	} catch (FileNotFoundException e1) {
	    System.out.println("File not found.  Please try again.");
	    try {
		input = Algorithms.getCommandsFromFile(Algorithms.getFileName(new Scanner(System.in)));
	    } catch (FileNotFoundException e) {
		
		input=new String[1];
		System.out.println("File not found.");
		e.printStackTrace();
		System.exit(1);
	    }
	}
	if(input.length !=5){
	    System.out.println("This program needs 5 pieces of input."+INSTRUCTIONS);
	    System.exit(1);
	}

	Group g = Group.getGroup(input[input.length-1]);
	if( g== null){
	    System.out.println("Last argument should be the name of a simple exceptional group;\n \""+input[input.length-1]+"\" is not.\n"+INSTRUCTIONS);
	    System.exit(1);
	}
	LiE lie = new LiE();
	
	    try {
		int[][] uMat = Algorithms.readLiEMatrix(lie.exec("unipRad("+input[3]+","+input[4]+")", OutputPref.HIDE));
		int[][] uPiece1 =Algorithms.readHandTyped(input[2]);
		if(uPiece1.length%2==1){
		    System.out.println("This version of the program \"Embedding\" " +
		    		"is customized for the case of a mapping into " +
		    		"a symplectic group.");
		    System.exit(1);
		}
		int[] a = Algorithms.rootFromString(input[0]);
	
		sunipotentEmbedding(style, input, g, uMat, uPiece1, a, Polynomial.create(input[1]));
		
		
		

		
		// FIXME Embedding - Embedding3 have this comment, but why shouldn't
		// there be a version that is applicable when the darn thing isn't
		// landing in a symplectic group?
		
		/*TODO
		 * this program was modified from Embedding3, do we want analogues of 
		 * Embedding2, Embedding which incorporate the Sp-specific elements
		 * of this program?
		 */
		
		
		
		// TODO eventually want to have functioning embedding program for general
		
		
		// TODO it would be nice to have a version where you set the basis and 
		// can then compute matrices of various elements without having to re-enter
		
		// TODO a sensible solution for a lot of these programs could be to have them read 
		// input from a plaintext file. then one can use one's prefered text editor to 
		// edit input.  
		
		// TODO implement a variety of output methods:  mathematica matrix, TeX matrix, 
		// and, for the ones customized to assume you're in Sp, expression in terms of 
		// e_{ij}', one that is as readable as possible onscreen.  Other?  
		// (Something Sage-friendly, perhaps.)
		
		
		
	    } catch (IOException e) {
		e.printStackTrace();
		System.exit(1);
	    } catch (NotARootException e) {
		e.printStackTrace();
		System.exit(1);
	    } catch (NonlinearException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	    }
	    
	
    }

    protected static void sunipotentEmbedding(OutputStyle style,
	    String[] input, Group g, int[][] uMat, int[][] uPiece1, int[] a, Polynomial argt)
	    throws NotARootException, NonlinearException {
	Unipotent u = new Unipotent(g,uMat, "u");
	Sunipotent v = Sunipotent.create(a, argt);
	
	// compute v^{-1} u v.  		
	Unipotent p = Unipotent.times(u, v);
	p.pushFarLeftAndRemove(p.getNumberOfTerms()-1);
	
	//Extract matrix 
	// declare array for underlying data
	Polynomial[][] m = new Polynomial[uPiece1.length][uPiece1.length];
	// create array of all the variables from the unipotent u, in the order they were given.
	String[] us = new String[uPiece1.length];
	for( int i = 0; i < us.length; i++)
		us[i]=u.defaultString("u", uPiece1[i]);
		
	    
	
	/*
	 * compute the matrix.
	 * Say the roots in uPiece1 are a1 ... a(2n)
	 */
	for( int i = 0; i < m.length; i++) // for each i 
	    m[i]=p.get(p.indexOf(uPiece1[i])) // get the sunipotent in p with root ai.
	    .getArgument()// get the argument
	    .coefficients(us);// get the vector of coefficients
	/* If q is a polynomial,and s is a string[]
	 * then q.coefficients(s) is a Polynomial[] of the same length, such that 
	 * q.coefficients(s)[j] is the coefficient of s[j] in q. 
	 */
	
	/* Write u[ai] for defaultString("u", ai).
	 * Then, at this point m[i][j] is the coefficient of u[aj]   
	 * the ai-element of v^{-1} u v.  
	 * 
	 * If v^{-1} u v = x_a1(u'[a1]) ... x_{a(n)}(u'[a(n)]) [*], 
	 * (with [*] representing an element of the commutator of U),
	 * then  u' = m.u,  with u', u being column vectors  
	 */
	
	
	// The code thus far computes a matrix using the 
	// F-basis x_{\alpha}(1).  But for half of all alpha it should be using 
	// x_{\alpha}( 1/N(\alpha', \alpha)).  
	
	// The matrix you really want is d^{-1} m d, 
	// where m is the matrix computed just above, and 
	// d = diag( 1, ... 1, N(alpha1, beta1), dots N( alpha n, beta n)),
	
	// An Issue:  if N(\alpha', \alpha) is 2, when will this take us outside the realm
	// of Polynomials with integral coefficients?
	
	// Answer:  Write m = A & B\\ C &D.  (A,B,C,D being n x n) Conjugation by d leaves A unaffected
	// multiplies entries in B by integers.  Now assume that C= 0.  Then D = _tA^{-1},
	// where _t denotes lower transpose.  This means that means D has integral 
	// coefficients as well.  (Keep in mind, we are computing a unipotent matrix, so 
	// we know det A =1.)  One easily constructs examples with nonintegral coefficients by 
	// taking C to be nonzero.  
	//FIXME add appropriate Sp-checking.
	
	/* Above we computed the matrix m(v).  It can be described thus.  The group U/(U,U)
	 * is a vector group and we map it to column vectors via a map c.
	 * 
	 * Then m(v).c(u) = c( v^{-1} u v).
	 * 
	 * We we work with Heisenberg and generalized Heisenberg groups, we prefer 
	 * to use row vectors.  Therefore, the matrix that we want 
	 * here is actually the transpose of m.   
	 */

	

	
	
	
	output(uPiece1, a, u, m, style);
    }

    private static void output(int[][] uPiece1, int[] a, Unipotent u,
	    Polynomial[][] m, OutputStyle style) {
	// TODO upgrade using switch 
	if(  style == OutputStyle.MATHEMATICA)
	    outputMathematica(uPiece1, a, u, m);
	else if( style == OutputStyle.SCREEN)
	    outputScreen(uPiece1, a, u, m);
	else 
	    System.out.println("Only screen and Mathematica output styles available.");
	// TODO add support for other styles as needed
    }

    private static void outputMathematica(int[][] uPiece1, int[] a,
	    Unipotent u, Polynomial[][] m) {
	StringBuilder result =new StringBuilder("\nx[{"+Algorithms.tupleToStringNoBrackets(a)+"},r_]:=");
	
	if( siegel(m)) {
	    normalizeByStructureConstants(uPiece1, u.getGroup(), m);
	    outputMathematicaStyle(result, m);
	}
	else{
	    result.append("Inverse["+matrixOfStructureConstantsMathematica(uPiece1, u.getGroup())+"].");
	    outputMathematicaStyle(result, m);
	    result.append("."+matrixOfStructureConstantsMathematica(uPiece1, u.getGroup()));
	}
	result.append(";\n");
	System.out.print(result.toString());
    }
    private static void outputScreen(int[][] uPiece1, int[] a,
	    Unipotent u, Polynomial[][] m) {
	StringBuilder result =new StringBuilder("\nx_{"+Algorithms.tupleToStringNoBrackets(a)+"}(r)=\n");
	
	if( siegel(m)) {
	    normalizeByStructureConstants(uPiece1, u.getGroup(), m);
	    outputForScreenReadability(result, m);
	}
	else{
	    result.append(matrixOfStructureConstantsScreen(uPiece1, u.getGroup())+"^{-1}.\n");
	    outputForScreenReadability(result, m);
	    result.append("."+matrixOfStructureConstantsScreen(uPiece1, u.getGroup()));
	}
	result.append("\n");
	System.out.print(result.toString());
    }

    private static String matrixOfStructureConstantsMathematica(int[][] uPiece1, Group g) {
	StringBuilder sb = new StringBuilder("DiagonalMatrix[{");
	for(int i = 0; i < uPiece1.length/2; i++){
	    sb.append("1,");
	}
	for(int i = uPiece1.length/2; i < uPiece1.length; i++){
	    sb.append(Integer.toString(g.getStructureConstant(uPiece1[uPiece1.length-1-i], uPiece1[i])));
	    if( i < uPiece1.length-1)
		sb.append(",");
	}
  
	sb.append("}]");
	return sb.toString();
    }
    private static String matrixOfStructureConstantsScreen(int[][] uPiece1, Group g) {
	StringBuilder sb = new StringBuilder("diag(");
	for(int i = 0; i < uPiece1.length/2; i++){
	    sb.append("1,");
	}
	for(int i = uPiece1.length/2; i < uPiece1.length; i++){
	    sb.append(Integer.toString(g.getStructureConstant(uPiece1[uPiece1.length-1-i], uPiece1[i])));
	    if( i < uPiece1.length-1)
		sb.append(",");
	}
  
	sb.append(")");
	return sb.toString();
    }

    private static void outputMathematicaStyle(StringBuilder result, Polynomial[][] m) {
	// output m transpose 
	// create stringbuilder for output 
	// output currently given in mathematica style
	result.append("{{");
	for( int i = 0; i < m.length; i++){
	    for( int j = 0 ; j < m.length; j++){
		result = result.append(m[j][i].toString());
		if( j == m.length - 1){
		    if( i !=m.length-1)
			result.append("},{");
		}
		else
		    result.append(",");

	    }
	}
	result.append("}}");
	
    }

    private static void outputForScreenReadability(StringBuilder result, Polynomial[][] m) {
	// output m transpose
	
	// create matrix of Strings 
	String[][] mStrings = new String[m.length][m[0].length];
	for(int i = 0 ; i < m.length; i++)
	    for( int j = 0 ; j < m[0].length; j++)
		mStrings[i][j]=m[i][j].toString();
	
	// compute width for each column  keep in mind that the matrix m which we have is 
	// actually the transpose of the one we will want to display, and therefore
	// the "columns" are actually rows in this matrix.
	int[] widths = new int[m.length];
	for( int i = 0; i < m.length; i++){
	    widths[i]=3;
	    for(int j =0; j < m[0].length; j++)
		widths[i]=Math.max(mStrings[i][j].length()+2, widths[i]);
	    
	}
	 // TODO Improve this code using Java's built in formatting features.
	
	
	
	for(int j = 0; j < mStrings[0].length; j++){
	    for( int i = 0 ; i < mStrings.length; i++)
		result.append(centered(widths[i], mStrings[i][j]));
	    result.append("\n");
	}
		
    }
    
    private static Object centered(int i, String string) {
	if( string.length() > i )
	    return string;
	StringBuilder result = new StringBuilder();

	for( int j = 0 ; j < (i-string.length())/2; j++ ) 
	    result.append(" ");
	result.append(string);
	for( int j = 0 ; j < (i-string.length()+1)/2; j++ ) 
	    result.append(" ");
	
	return result.toString();
    }

    private static void normalizeByStructureConstants(int[][] uPiece1,
	    Group g, Polynomial[][] m) {
	// form a matrix which contains the structure constants by which we will need to multiply.
	int[] structureConstants = new int[uPiece1.length/2];
	for(int i = 0; i < uPiece1.length/2; i++)
	    structureConstants[uPiece1.length/2-1-i]=g.getStructureConstant(uPiece1[i], uPiece1[uPiece1.length-1-i]);
	
	// do the upper right ("B")
	for( int i = 0; i < m.length/2; i++)
	    for( int j = 0 ; j < m.length/2; j++)
		m[i+m.length/2][j]=m[i+m.length/2][j].times(structureConstants[i]);
	
	// do the lower right ("D")
	for( int i = 0; i < m.length/2; i++)
	    for( int j = 0 ; j < m.length/2; j++)
		m[i+m.length/2][j+m.length/2]=m[i+m.length/2][j+m.length/2].times(structureConstants[i]).quotientBy(structureConstants[j]);
    }

    private static final boolean siegel(Polynomial[][] m) {
	// check that the lower left portion ("C") is zero.  If it ain't this program may produce 
	// an incorrect result.
	/*Because the matrix we want is m transpose, "C" corresponds to indices i,j with 
	 * i < n, j \ge n.  (Here, m is assumed to be 2n x 2n.)
	 * 
	 */
	for( int i = 0; i < m.length/2; i++)
	    for( int j = 0 ; j < m.length/2; j++)
		if(!(m[i][j+m.length/2]==Polynomial.create(0))){
		    
		    return false;
		}
	return true;
    }

}
